/*
 * Copyright (c) 2010 Vadim Zhukov <persgray@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


#include "common.h"

#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "logmsg.h"
#include "sensor.h"
#include "timestamp.h"

/* To be included last */
#include "sensor_list.h"


static struct sensor_routine	*sensor_find_routine(const char *,
				    const char **);


struct sensor_routine *
sensor_find_routine(const char *sname, const char **endptr) {
	int		 i;
	const char	*end;

	DPRINTFX(3, "sensor_find_routine: given: %s", sname);
	if (strncmp(sname, "/sensor/", 8)) {
		errno = EINVAL;
		return NULL;
	}
	sname += 8;
	for (end = sname; *end != '\0' && *end != '/'; end++);
	DPRINTFX(3, "sensor_find_routine: typename starts at: %s", sname);
	DPRINTFX(3, "sensor_find_routine: typename length: %d",
	    (int)(end - sname));
	/* XXX: Should do more name validity checks */
	for (i = 0; i < nitems(sensor_routines); i++) {
		DPRINTFX(3, "sensor_find_routine: comparing with: %s",
		    sensor_routines[i].name);
		if (strlen(sensor_routines[i].name) != end - sname)
			continue;
		if (memcmp(sensor_routines[i].name, sname, end - sname) == 0) {
			if (endptr != NULL)
				*endptr = end;
			return &sensor_routines[i];
		}
	}
	return NULL;
}

/*
 * Should be called after instantinating a new sensor or when its parameter
 * change.
 *
 * Return 0 on success, -1 on failure (with errno set).
 */
int
sensor_init(struct wsensor *s) {
	struct sensor_routine	*sr;

	sr = sensor_find_routine(s->name, &s->params);
	if (sr == NULL) {
		errno = ESRCH;
		return -1;
	}
	if (sr->init != NULL)
		if ((*sr->init)(s) == -1)
			return -1;
	s->flags |= sr->flags;
	s->get_line = sr->get;
	return 0;
}

int
sensor_uninit(struct wsensor *s) {
	struct sensor_routine	*sr;

	sr = sensor_find_routine(s->name, NULL);
	if (sr == NULL) {
		errno = ESRCH;
		return -1;
	}
	if (sr->uninit != NULL)
		if ((*sr->uninit)(s) == -1)
			return -1;
	return 0;
}

/*
 * Actually just run the handler set by sensor_init().
 */
struct log_msg *
sensor_get_line(struct wsensor *s) {
	struct log_msg	*m;
	int		 oerrno;

	if (s->get_line == NULL) {
		errno = EBADF;
		return NULL;
	}

	/*
	 * XXX: FIXME: for non-gauge sensors we may have many duty cycles of
	 * log_msg_new() + log_msg_free().
	 */
	m = log_msg_new(NULL);
	if (m == NULL)
		goto err;
	m->producer = strdup(s->name);
	if (m->producer == NULL)
		goto err;
	if ((*s->get_line)(s, m) == -1)
		goto err;
	if (!(s->flags & SENSOR_F_NOLOG))
		m->hdr->flags |= LOG_MSG_ACTION_LOG;
	return m;

err:
	oerrno = errno;
	log_msg_free(m);
	errno = oerrno;
	return NULL;
}


/*
 * Extracts timestamp and producer name, and place them in log_msg structure.
 *
 * Return 0 on success, or -1 (with setting errno) on error. If exited with
 * error, "m" and "m->hdr" are in undetermined state.
 */
int
sensor_convert_log_line(const char *s, size_t len, struct log_msg *m) {
	struct	 tm tm;
	int	 oerrno;
	const	 char *e, *b, *errstr;

	DPRINTFX(5, "sensor_convert_log_line(\"%s\")", s);

	/* Get timestamp */
	e = strptime(s, "%b %d %T ", &tm);
	len -= (e - s);
	/* XXX: Year? */
	if (e == NULL) {
		DPRINTF(1, "sensor_convert_log_line: strptime");
		return -1;
	}
	tm.tm_isdst = -1;
	m->hdr->timestamp = timestamp_pack(mktime(&tm));

	/* Skip hostname */
	s = e;
	for (; *e != ' '; e++) {
		if (*e == '\0') {
			errno = EINVAL;
			return -1;
		}
		e++;
	}
	e++;
	/* (unused) len -= (e - s); */

	/* Get producer */
	s = e;
	for (; *e != ':'; e++) {
		if (*e == '\0') {
			errno = EINVAL;
			return -1;
		}
	}
	/* XXX: This is an empty line, what to do? Bail out?
	if (e[1] == '\0' || e[2] == '\0') {
		errno = EINVAL;
		return -1;
	}
	*/
	for (b = s; b < e && *b != '['; b++);
	/* Do not bail out if getting PID fails, just not interpret it */
	m->hdr->source_pid = 0;
	if (b != e && e[-1] == ']')
		/* XXX: Did not found any way to get maximum PID value in OS */
		m->hdr->source_pid = (uint32_t)strtonum(b + 1, 1, INT_MAX,
		    &errstr);
	if (m->hdr->source_pid == 0)
		b = e;
	m->source = malloc(b - s + 1);
	if (m->source == NULL)
		return -1;
	memcpy(m->source, s, b - s);
	m->source[b - s] = '\0';

	m->text = strdup(e);
	if (m->text == NULL) {
		oerrno = errno;
		free(m->source);
		errno = oerrno;
		return -1;
	}

	return 0;
}
